package cn.myzf.netty.client.handler;


import com.alibaba.fastjson.JSON;
import com.hs.netty.common.entity.MonitorVoDO;
import com.hs.netty.common.entity.NettyMessageDO;
import com.hs.netty.common.protobuf.Command;
import com.hs.netty.common.protobuf.Message;
import com.hs.netty.common.util.ReflectionUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class HeartHandler extends SimpleChannelInboundHandler<Message.MessageBase> {
    public Logger log = LoggerFactory.getLogger(this.getClass());
    private final static String DEVICENAME = ReflectionUtil.getValue("spring.netty.deviceName");
    private final DateFormat sf = new SimpleDateFormat("HH:mm:ss");
    private NettyClient nettyClient;
    private int heartbeatCount = 0;
    private final static String CLIENTID = ReflectionUtil.getValue("spring.netty.clientId");
    private long ccTime = 0;//缓存发送时间 单位毫秒

    // 定义客户端没有收到服务端的pong消息的最大次数
    private static final int MAX_UN_REC_PONG_TIMES = 5;

    // 多长时间未请求后，发送心跳
    private static final int WRITE_WAIT_SECONDS = 5;//暂时未使用

    // 隔N秒后重连
    private static final int RE_CONN_WAIT_SECONDS = 3;//暂时未使用

    // 客户端连续N次没有收到服务端的pong消息  计数器
    private int unRecPongTimes = 0;

    /**
     * @param nettyClient
     */
    public HeartHandler(NettyClient nettyClient) {
        this.nettyClient = nettyClient;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            String type = "";
            if (event.state() == IdleState.READER_IDLE) {
                type = "read idle";
            } else if (event.state() == IdleState.WRITER_IDLE) {
                type = "write idle";
            } else if (event.state() == IdleState.ALL_IDLE) {
                type = "all idle";
            }
            if (unRecPongTimes < MAX_UN_REC_PONG_TIMES) {
                sendPingMsg(ctx, CLIENTID);
                unRecPongTimes++;
            } else {
                ctx.channel().close();
            }

        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    /**
     * 发送ping消息
     *
     * @param context
     */
    protected void sendPingMsg(ChannelHandlerContext context, String client) {
        heartbeatCount++;
        ccTime = System.currentTimeMillis();
        context.writeAndFlush(
                Message.MessageBase.newBuilder()
                        .setClientId(client)
                        .setCmd(Command.CommandType.PING)
                        .setData(String.valueOf(ccTime))
                        .build()
        );

        //	log.info("Client sent ping msg to " + context.channel().remoteAddress() + ", count: " + heartbeatCount);
    }

    /**
     * @return返回微秒 如果有更深一步的业务可使用
     */
    public static Long getmicTime() {
        Long cutime = System.currentTimeMillis() * 1000; // 微秒
        Long nanoTime = System.nanoTime(); // 纳秒
        return cutime + (nanoTime - nanoTime / 1000000 * 1000000) / 1000;
    }

    /**
     * 处理断开重连
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("与Netty服务器连接断开  5s后重新尝试连接....");
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(() -> nettyClient.doConnect(new Bootstrap(), eventLoop), 5, TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Message.MessageBase msg) throws Exception {
        if (msg.getCmd().equals(Command.CommandType.AUTH_BACK)) {
            log.info("AUTH_BACK:" + msg.getData());
            NettyMessageDO nettyMessageDO = new NettyMessageDO();
            nettyMessageDO.setClientName(CLIENTID);
            nettyMessageDO.setDate(new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date()));
            nettyMessageDO.setDeviceName(DEVICENAME);
            ctx.writeAndFlush(
                    Message.MessageBase.newBuilder()
                            .setClientId(CLIENTID)
                            .setCmd(Command.CommandType.PUSH_DATA)
                            .setData(JSON.toJSONString(nettyMessageDO))
                            .build()
            );
        } else if (msg.getCmd().equals(Command.CommandType.PING)) {
            //接收到server发送的ping指令
            log.info("ping------------" + msg.getData());

        } else if (msg.getCmd().equals(Command.CommandType.PONG)) {
            //接收到server发送的pong指令
            unRecPongTimes = 0;
            //计算ping值
            long ping = (System.currentTimeMillis() - ccTime) / 2;
            log.info(msg.getData());
            MonitorVoDO monitorVoDO = new MonitorVoDO();
            monitorVoDO.setClientId(CLIENTID);
            monitorVoDO.setDateList(sf.format(new Date()));
            monitorVoDO.setValueList(ping);
            sendNettyMessage("heart:monitor:" + CLIENTID, JSON.toJSONString(monitorVoDO));
        } else if (msg.getCmd().equals(Command.CommandType.PUSH_DATA)) {
            //接收到server推送数据
            log.info("PUSH_DATA:" + msg.getData());

        } else if (msg.getCmd().equals(Command.CommandType.PUSH_DATA_BACK)) {
            //接收到server返回数据
            log.info("PUSH_DATA_BACK:" + msg.getData());

        } else {
            log.info(msg.getData());
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.debug("发生了异常 异常信息：" + cause.getMessage());
    }

    public void sendNettyMessage(String tableName, String data) {
        Map api_header = new HashMap<String, String>();
        Map paramMap = new HashMap();
        paramMap.put("tableName", tableName);
        paramMap.put("data", data);
        try {
//            HttpsUtil.post("https://" + Config.api_iot + ".hang-shu.com/v1/netty/sendNettyMessage", api_header, paramMap);
//            HttpsUtil.post("https://" + Config.api + ".hang-shu.com/v1/external/wst/insertWstGroupExceptionData", api_header, paramMap);
            log.info("发送数据存入 Redis--------------------------------------发送心跳数据成功!!!");
        } catch (Exception e) {
            log.info("发送数据存入 Mysql--------------------------------------发送心跳数据成功!!!");
            e.printStackTrace();
        }
    }
}
